#ifndef SCALE_ROUTING_H
#define SCALE_ROUTING_H

# include <package.H>
# include <statistics.H>
# include <tpl_sort_utils.H>
# include <cmath>

/**
  * \brief Clase que contiene el algoritmo escalera.
  * @author Anna Lezama
  */

struct Way {
  Direction d;
  double value;
};


struct Cmp_Max_Way
{
  bool operator()(const Way & w1, const Way & w2)
  {
    return w1.value > w2.value;
  }

};

struct Cmp_Min_Way
{
  bool operator()(const Way & w1, const Way & w2)
  {
    return w1.value < w2.value;
  }

};

class Scale_Routing
{
  gsl_rng * rng;

public:

  Scale_Routing()
    : rng(gsl_rng_alloc(gsl_rng_mt19937))
  {
    gsl_rng_set(rng, time(NULL));
  }


  ~Scale_Routing()
  {
    gsl_rng_free(rng);
  }
  /**
    * Operaci&oacute;n que ejecuta el enrutamiento escalera.
    * @param g La malla en la que se est&aaccute; trabajando.
    * @param node El nodo donde se encuentran los paquetes que se desean enrutar.
    */
  void operator()(Grid & g, Grid::Node * node)
  {

    if (node->get_clients_list().is_empty())
      return;

    g.reset_all_arcs();

    Statistics * statistics = Statistics::get_instance();
    std::string node_name = node->to_string();
    bool routed;

    DynDlist<Package *> & list = node->get_clients_list();

    while(not list.is_empty())
      {

        Package * curr_package = list.remove_first();

        Grid::Node * destination = curr_package->get_target_node();

        double x = destination->get_position().get_x().get_d() - node->get_position().get_x().get_d();
        double y = destination->get_position().get_y().get_d() - node->get_position().get_y().get_d();

        x = abs(x);
        y = abs(y);

        DynDlist<Way> favorable_directions;
        double x_value = 0;
        double y_value = 0;

        Direction favorable_direction;
        Way way;

        if (curr_package->get_h_favorable_direction() != Num_Directions){
            favorable_direction = curr_package->get_h_favorable_direction();
            x_value = x / (x + y);
            switch (favorable_direction){
              case East:
                way.d = East;
                way.value = x_value;
                break;
              case West:
                way.d = West;
                way.value = x_value;
                break;
              }
            favorable_directions.append(way);
          }

        if (curr_package->get_v_favorable_direction() != Num_Directions){
            favorable_direction = curr_package->get_v_favorable_direction();
            y_value = y / (x + y);
            switch (favorable_direction){
              case North:
                way.d = North;
                way.value = y_value;
                break;
              case South:
                way.d = South;
                way.value = y_value;
                break;
              }
            favorable_directions.append(way);
          }

        Way * max = search_max <Way, Cmp_Max_Way>(favorable_directions);

        DynDlist <Way> max_directions;
        //max_directions.append(*max); Max se inserta dos veces
        for (DynDlist<Way>::Iterator it2(favorable_directions); it2.has_current(); ){
            Way & w = it2.get_current();
            it2.next();
            if (max->value == w.value){
                max_directions.append(w);
                favorable_directions.remove(w);
              }
          }

        Way routed_to;

        routed = false;

        Grid::Arc * link = NULL;
        while (not max_directions.is_empty())
          {
            Way & w = max_directions[gsl_rng_uniform_int(rng, max_directions.size())];
            routed_to = w;
            //REMOVE
            max_directions.remove(w);

            link = NULL;
            link = g.get_arc_by_rout(node, routed_to.d);
            if (link not_eq NULL and not g.is_arc_busy_for_node(link, node))
              {
                routed = true;
                break;
              }
          }


        if (routed){
            Grid::Node * tgt = g.get_connected_node(link, node);
            curr_package->rout_to(tgt);
            g.set_arc_busy_for_node(link, node, curr_package);
            curr_package->inc_ttl();
            curr_package->inc_num_routings();
            node->inc_num_nodes_routed();
//            it.next();
//            list.remove(curr_package);
            std::string pkn = curr_package->to_string();
            std::string tgt_name = tgt->to_string();
            std::string stp = gnu::autosprintf("%s es enrutado de %s a %s",
                                               pkn.c_str(), node_name.c_str(), tgt_name.c_str());
            std::string stn = gnu::autosprintf("%s ha enrutado al paquete %s hacia %s",
                                               node_name.c_str(), pkn.c_str(), tgt_name.c_str());
            statistics->add_statistic_to_package(pkn, stp);
            statistics->add_statistic_to_node(node_name, stn);
            continue;
          }

        //Buscar la otra direccion favorable

        routed = false;
        while (not favorable_directions.is_empty())
          {
            Way & w = favorable_directions[gsl_rng_uniform_int(rng, favorable_directions.size())];
            routed_to = w;

            //REMOVE
            favorable_directions.remove(w);

            link = NULL;
            link = g.get_arc_by_rout(node, routed_to.d);
            if (link not_eq NULL and not g.is_arc_busy_for_node(link, node))
              {
                routed = true;
                break;
              }
          }

        if (routed){
            //Enrutar favorable
            Grid::Node * tgt = g.get_connected_node(link, node);
            curr_package->rout_to(tgt);
            g.set_arc_busy_for_node(link, node, curr_package);
            curr_package->inc_ttl();
            curr_package->inc_num_routings();
            node->inc_num_nodes_routed();
//            it.next();
//            list.remove(curr_package);
            std::string pkn = curr_package->to_string();
            std::string tgt_name = tgt->to_string();
            std::string stp = gnu::autosprintf("%s es enrutado de %s a %s",
                                               pkn.c_str(), node_name.c_str(), tgt_name.c_str());
            std::string stn = gnu::autosprintf("%s ha enrutado al paquete %s hacia %s",
                                               node_name.c_str(), pkn.c_str(), tgt_name.c_str());
            statistics->add_statistic_to_package(pkn, stp);
            statistics->add_statistic_to_node(node_name, stn);
            continue;
          }

        //Calcular la direcciones no favorables

        DynDlist <Way> deflection_directions;

        switch(curr_package->get_h_favorable_direction())
          {
          case East:
          case West:
            {
              Way way;
              way.value = (x + 1) / ((y + 1) + (x + 1));
              way.d = get_reflect_direction(curr_package->get_h_favorable_direction());
              deflection_directions.append(way);
            }
            break;
          default:
            Way way;
            way.value = (x + 1) / ((y + 1) + (x + 1));
            way.d = East;
            deflection_directions.append(way);
            way.d = West;
            deflection_directions.append(way);
          }

        switch(curr_package->get_v_favorable_direction())
          {
          case North:
          case South:
            {
              Way way;
              way.value = (y + 1) / ((y + 1) + (x + 1));
              way.d = get_reflect_direction(curr_package->get_v_favorable_direction());
              deflection_directions.append(way);
            }
            break;
          default:
            Way way;
            way.value = (y + 1) / ((y + 1) + (x + 1));
            way.d = South;
            deflection_directions.append(way);
            way.d = North;
            deflection_directions.append(way);
          }

        Way * min = search_min <Way, Cmp_Min_Way>(deflection_directions);

        DynDlist <Way> min_directions;

        for (DynDlist<Way>::Iterator it5(deflection_directions); it5.has_current(); ){
            Way & w = it5.get_current();
            it5.next();
            if (min->value == w.value){
                min_directions.append(w);
                deflection_directions.remove(w);
              }
          }


        routed = false;
        while (not min_directions.is_empty())
          {
            Way & w = min_directions[gsl_rng_uniform_int(rng, min_directions.size())];
            routed_to = w;
            //REMOVE
            min_directions.remove(w);

            link = NULL;
            link = g.get_arc_by_rout(node, routed_to.d);
            if (link not_eq NULL and not g.is_arc_busy_for_node(link, node))
              {
                routed = true;
                break;
              }
          }


        if (routed){
            Grid::Node * tgt = g.get_connected_node(link, node);
            curr_package->rout_to(tgt);
            g.set_arc_busy_for_node(link, node, curr_package);
            curr_package->inc_ttl();
//            it.next();
            curr_package->inc_num_deflections();
            node->inc_num_nodes_deflected();
            statistics->set_max_deflections(curr_package->get_num_deflections());
//            list.remove(curr_package);
            std::string pkn = curr_package->to_string();
            std::string tgt_name = tgt->to_string();
            std::string stp = gnu::autosprintf("%s es deflectado de %s a %s",
                                               pkn.c_str(), node_name.c_str(), tgt_name.c_str());
            std::string stn = gnu::autosprintf("%s ha deflectado al paquete %s hacia %s",
                                               node_name.c_str(), pkn.c_str(), tgt_name.c_str());
            statistics->add_statistic_to_package(pkn, stp);
            statistics->add_statistic_to_node(node_name, stn);
            continue;
          }

        routed = false;
        while (not deflection_directions.is_empty())
          {
            Way & w = deflection_directions[gsl_rng_uniform_int(rng, deflection_directions.size())];
            routed_to = w;
            //REMOVE
            deflection_directions.remove(w);

            link = NULL;
            link = g.get_arc_by_rout(node, routed_to.d);
            if (link not_eq NULL and not g.is_arc_busy_for_node(link, node))
              {
                routed = true;
                break;
              }
          }


        if (routed){
            Grid::Node * tgt = g.get_connected_node(link, node);
            curr_package->rout_to(tgt);
            g.set_arc_busy_for_node(link, node, curr_package);
            curr_package->inc_ttl();
//            it.next();
            curr_package->inc_num_deflections();
            node->inc_num_nodes_deflected();
            statistics->set_max_deflections(curr_package->get_num_deflections());
//            list.remove(curr_package);
            std::string pkn = curr_package->to_string();
            std::string tgt_name = tgt->to_string();
            std::string stp = gnu::autosprintf("%s es deflectado de %s a %s",
                                               pkn.c_str(), node_name.c_str(), tgt_name.c_str());
            std::string stn = gnu::autosprintf("%s ha deflectado al paquete %s hacia %s",
                                               node_name.c_str(), pkn.c_str(), tgt_name.c_str());
            statistics->add_statistic_to_package(pkn, stp);
            statistics->add_statistic_to_node(node_name, stn);
            continue;
          }

        //TODO: Puede existir otra direccion para deflectar

        Grid::Arc * first_free_link = g.get_first_free_link(node);
        Grid::Node * tgt = g.get_connected_node(first_free_link, node);
        curr_package->rout_to(tgt);
        g.set_arc_busy_for_node(first_free_link, node, curr_package);
        curr_package->inc_ttl();
//        it.next();
        curr_package->inc_num_deflections();
        node->inc_num_nodes_deflected();
        statistics->set_max_deflections(curr_package->get_num_deflections());
//        list.remove(curr_package);
        std::string pkn = curr_package->to_string();
        std::string tgt_name = tgt->to_string();
        std::string stp = gnu::autosprintf("%s es deflectado de %s a %s",
                                           pkn.c_str(), node_name.c_str(), tgt_name.c_str());
        std::string stn = gnu::autosprintf("%s ha deflectado al paquete %s hacia %s",
                                           node_name.c_str(), pkn.c_str(), tgt_name.c_str());
        statistics->add_statistic_to_package(pkn, stp);
        statistics->add_statistic_to_node(node_name, stn);
      }
  }
};

#endif // SCALE_ROUTING_H
